01. 列表

列表

到目前为止,我们一直在处理各种单独的数据,比如字符串和数字。但当我们使用数据集合时,还可以编写出更强大的程序。现在我们先介绍一下第一种数据集合:列表。

python_versions = [1.0, 1.5, 1.6, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6]

上面这行代码定义了一个变量 python_versions ,其中包含一系列浮点数。列表中的每个元素都表示 Python 的一个版本号(Python 的版本一直在升级,因此有很多不同的版本号)。列表使用方括号( [ ] )定义,列表内的元素用逗号分开。

我们可以通过索引来查找列表中的各个元素,比如针对上面这个列表,我们可以按照如下所示的方法查找版本号:

>>> python_versions[0]
1.0

>>> python_versions[1]
1.5

>>> python_versions[7]
2.4

请注意列表中的第一个元素 1.0 的索引编号为 0 ,而不是 1。有许多编程语言都遵循这个惯例,我们将其称为"零索引"。如果这种说法让你难以理解,你也可以这样理解:元素的索引编号代表元素与列表开头的距离。第一个元素距离开头有 0 个元素,第二个元素有一个元素,以此类推。

当然,除了这种从列表开头进行索引的方法,我们也可以从列表的末尾进行索引。
要从列表的末尾索引需要使用负索引。以我们在上方定义的列表为例,我们可以通过下面这种方法得到最新的 Python 版本:

>>> python_versions[-1]
3.6

索引 -1 是指列表的最后一个元素, -2 是倒数第二个,以此类推。

索引错误

如果尝试索引列表中不存在的元素,将导致列表索引异常(List Index Exception)。这条信息表示 Python 提醒你正在尝试访问一个列表中不存在的元素。

例如,我们定义以下列表:

>>> my_list = ['a','b','c','d','e']

这个列表中有五个元素,索引编号分别是 0、1、2、3 和 4

>>> my_list[4]
'e'

如大家所见, my_list [4] 返回这个列表的最后一个元素。但是当我们尝试访问索引编号为 5 的元素时,会出现什么结果?

>>> my_list[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

因为列表中只包含 5 个元素,尝试访问索引编号为 5 的元素其实是要求 Python 给出该列表中的第 6 个元素。由于这个元素并不存在,所以导致了一个 IndexError

索引错误十分常见,尤其是当你还不习惯在 Python 中进行索引的时候。刚开始你可能会经常收到这些错误信息,但在慢慢熟悉 Python 后,这样的错误就会很少出现了。导致索引错误最常见的一个原因是在进行索引时编号差 1(比如在上方的例子中, my_list[5] 会导致索引错误,因为列表中最后一个元素的编号实际上是 4),但也存在一些其他原因。因此我们建议你使用 print 函数来打印你想要索引的元素,以此进行纠错,这可以提醒你的索引是否有差,差是多少。

练习:列表索引

请完成函数 how_many_days ,其将输入一个表示月份的数字,并返回该月份的天数。我们定义的 days_in_month 是一个包含各月天数的列表。例如, how_many_days(8) 应该返回 31,因为第八个月,即八月,有 31 天。

记住索引编号从零开始!

(提示:目前函数还没有学过,你可以参考下一课: 函数 来了解一些基本知识。本节中编程习题的解决方案可以在下一小节: 解决方案:列表 中查看。)

Start Quiz:

def how_many_days(month_number):
    """Returns the number of days in a month.
    WARNING: This function doesn't account for leap years!
    """
    days_in_month = [31,28,31,30,31,30,31,31,30,31,30,31]
    #todo: return the correct value
    
# This test case should print 31, the number of days in the eighth month, August
print(how_many_days(8))

列表切片

除了从列表访问各个元素外,我们还可以使用 Python 的切片符号来访问列表的子序列。大家来看一下这个月份列表,

months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']

我们可以从月份列表中切片出一年的第三季度,如下所示:

>>> q3 = months[6:9]
>>> print(q3)
['July', 'August', 'September']
>>> print(months)
['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']

`q3 = months[6:9]`

q3 = months[6:9]

冒号左侧的索引编号 6 是切片开始的位置。切片持续到第二个索引编号 9(请注意,切片不包括索引编号为 9 的元素,但包括编号为 6 的元素,以此类推)。

切片简化方式

当然我们也有一些简化切片代码的方法。如果你想获得一个从原始列表开头开始的子列表,或者一个在原始列表的末尾结束的子列表,可以采用如下所示的方法来快捷开始或结束索引:

>>> first_half = months[:6]
>>> print(first_half)
['January', 'February', 'March', 'April', 'May', 'June']
>>> second_half = months[6:]
>>> print(second_half)
['July', 'August', 'September', 'October', 'November', 'December']

练习:列表切片

请使用列表切片符号从下面这个列表中选择三个最近的日期。提示:负索引也适用于列表切片。

Start Quiz:

eclipse_dates = ['June 21, 2001', 'December 4, 2002', 'November 23, 2003',
                 'March 29, 2006', 'August 1, 2008', 'July 22, 2009',
                 'July 11, 2010', 'November 13, 2012', 'March 20, 2015',
                 'March 9, 2016']
                 
                 
# TODO: Modify this line so it prints the last three elements of the list
print(eclipse_dates)

列表、字符串和可变性

string float int 一样, list 也是一种类型。在我们看到的所有类型中,列表与字符串最为相似:这两种类型都支持索引、切片、 len 函数和 in 运算符。

>>> sample_string = "And Now For Something Completely Different"
>>> sample_list = ['Graham', 'John', 'Terry', 'Eric', 'Terry', 'Michael']
>>> sample_string[4]
'N'
>>> sample_list[4]
'Terry'
>>> sample_string[12:21]
'Something'
>>> sample_list[2:4]
['Terry', 'Eric']
>>> len(sample_string)
42
>>> len(sample_list)
6
>>> 'thing' in sample_string
True
>>> 'Rowan' in sample_list
False

那么列表与字符串有什么不同?其中最明显的区别是字符串为字母序列,而列表的元素可以是 任何 类型的对象。更细微的区别是列表可以被修改,但字符串不能:

>>> sample_list[3] = 'Eric'
>>> print(sample_list)
['Graham', 'John', 'Terry', 'Eric', 'Terry', 'Michael']
>>> sample_string[8] = 'f'
TypeError: 'str' object does not support item assignment

表示对象可否修改的术语是 可变性 (Mutability)。列表是可变的,而字符串不可变。接下来,我们将探讨可以在列表中使用的方法和函数,同时将在程序中利用列表的可变性。

可变性匹配练习

QUIZ QUESTION: :

假设我们有以下两个表达式, sentence1 sentence2

sentence1 = "I wish to register a complaint."
sentence2 = ["I", "wish", "to", "register", "a", "complaint", "."]

将下面的 python 代码与修改后的 sentence1 sentence2 值相匹配。如果代码导致错误,则与
Error 匹配。

ANSWER CHOICES:



Python code

sentence1 sentence2 的值

["Our Majesty", "wish", "to", "register", "a", "complaint", "."]

["I", "wish", "to", "register", "a", "complaint", "!"]

“I wish to register a complaint!”

["We", "want", "to", "register", "a", “complaint”, "."]

Error

SOLUTION:

Python code

sentence1 sentence2 的值

["Our Majesty", "wish", "to", "register", "a", "complaint", "."]

["I", "wish", "to", "register", "a", "complaint", "!"]

["We", "want", "to", "register", "a", “complaint”, "."]

Error

保存列表的变量

之前,当创建一个具有不可变对象的变量时,该不可变对象的值即被保存在内存中。例如在下面这个示例中,我们创建了一个值为 "Old Woman" 的变量 name ,并将其赋值给另一个变量 person

>>> name = "Old Woman"
>>> person = name
>>> name = "Dennis"
>>> print(name)
Dennis
>>> print(person)
Old Woman

在第二行代码中,字符串 "Old Woman" 已经为 person 赋值。因此当我们在后面为 name 重新赋值,将其更新为 "Dennis" 时,并不会影响到 person 的值。

列表与字符串不同,它们是可变的。在下面这个示例中,我们创建了一个名为 dish 的列表,列表中包含了一家咖啡厅的菜肴。我们将这个列表赋值给变量 mr_buns_order 。当我们由于一种食材不可用而更改(转变)列表 dish 时,会同时影响 dish mr_buns_order

>>> dish = ["Spam", "Spam", "Spam", "Spam", "Spam", "Spam", "baked beans", "Spam", "Spam", "Spam", "Spam"]
>>> mr_buns_order = dish
>>> print(dish)
['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'baked beans', 'Spam', 'Spam', 'Spam', 'Spam']
>>> print(mr_buns_order)
['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'baked beans', 'Spam', 'Spam', 'Spam', 'Spam']
>>> dish[6] = "Spam" #baked beans are off
>>> print(mr_buns_order)
['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']
>>> print(dish)
['Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam', 'Spam']

dish mr_buns_order 是同一底层列表的两个变量名,我们可以使用任一名称来访问和更改该列表。

包含可变和不可变对象的变量运行方式非常不同,你必须十分注意这一点。
你需要不断进行试验,通过 print 函数来检查你的代码,以确保程序可以正确处理数据。

使用列表

下面是一些可以与列表一起使用的函数:

len(some_list)

返回 some_list 中的元素个数

max(some_list)

返回列表中的最大元素。最大元素的确定取决于列表中的对象类型。数字列表中的最大元素是最大的数字:

>>> batch_sizes = [15, 6, 89, 34, 65, 35]
>>> max(batch_sizes)
89

字符串列表的最大元素是首字母顺序排在最后的一个元素:

>>> python_varieties = ['Burmese Python', 'African Rock Python', 'Ball Python', 'Reticulated Python', 'Angolan Python']
>>> max(python_varieties)
'Reticulated Python'

这是因为 max 函数采用比较运算符 > 定义。有许多非数字类型都可以使用 > 运算符进行比较。如果正在处理的对象可以用 > 比较,那么在这类对象的列表中便可以使用 max 函数。对于字符串来说,比较标准是首字母顺序,因此上面示例中列表的最大值就是首字母顺序排在最后的元素。

当一个列表中包含不同类型的元素,并且这些类型无法进行比较时, max 函数也将无法使用:

>>> max([42, 'African Swallow'])
TypeError: unorderable types: str() > int()


这是因为 max 函数采用 > 定义,如果无法比较列表中的两个对象,则无法确定最大元素。

min(some_list)

返回列表中的最小元素。 min max 相反。

sorted(some_list)

按从小到大的顺序返回 some_list 的副本,同时保持 some_list 不变。可以通过添加可选参数 reverse = True 按从大到小的顺序排序。

>>> sorted(batch_sizes)
[6, 15, 34, 35, 65, 89]
>>> sorted(batch_sizes, reverse=True)
[89, 65, 35, 34, 15, 6]
>>> print(batch_sizes)
[15, 6, 89, 34, 65, 35]

连接列表

下面我们来介绍一个新的字符串方法 join ,使用示例如下:

>>> nautical_directions = "\n".join(["fore", "aft", "starboard", "port"])
>>> print(nautical_directions)
fore
aft
starboard
port

join 将一个列表作为参数,返回一个由分隔符字符串连接列表元素组成的字符串。在这个示例中,我们使用字符串 \n 作为分隔符,以便使每个元素之间有一个换行符。

我们也可以和 .join 配合使用其他字符串(而不是 '\n' )。例如:

>>> names = ["García", "O'Kelly", "Davis"]
>>> "-".join(names)
"García-O'Kelly-Davis"

注意,务必用逗号 ( , ) 隔开连接列表中的每个元素。如果忘记隔开,尽管不会导致错误,但也会使你无法获得理想的结果。下面的例子中,"García” 和 "O'Kelly” 之间没有逗号,所以出现了以下结果:

>>> names = ["García" "O'Kelly", "Davis"]
>>> "-".join(names)
"GarcíaO'Kelly-Davis"

你有没有注意到 "García" 和 "O'Kelly" 之间的 '-' 分隔符消失了?这是因为 Python 默认按字符串的字面形式进行连接。如果 .join 返回的结果与你的预期不同,那么你最好检查是否丢失了逗号。

你还要注意的是,如果你尝试在列表中加入字符串以外的其他任何内容, join 会触发错误。例如:

>>> stuff = ["thing", 42, "nope"]
>>> " and ".join(stuff)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 1: expected str instance, int found

追加到列表

列表对象的 append 方法在列表末尾添加一个元素。

>>> python_varieties.append('Blood Python')
>>> print(python_varieties)
['Burmese Python', 'African Rock Python', 'Ball Python', 'Reticulated Python', 'Angolan Python', 'Blood Python']

练习:前三名

编写一个函数 top_three ,该函数以列表为参数,返回三个最大元素的列表。例如, top_three([2,3,5,6,8,4,2,1]) == [8, 6, 5]

Start Quiz:

def top_three(input_list):
    """Returns a list of the three largest elements input_list in order from largest to smallest.

    If input_list has fewer than three elements, return input_list element sorted largest to smallest/
    """
    # TODO: implement this function

练习:中位数(Median)

此练习中的函数 median 返回输入列表的中值。但该函数只适用于具有奇数个元素的列表。现在请你修改函数,当为 median 输入具有偶数个元素的列表时,该函数可返回两个中心元素的平均值。练习中提供的用例可以使你测试预期结果。

Start Quiz:

def median(numbers):
    numbers.sort() #The sort method sorts a list directly, rather than returning a new sorted list
    middle_index = int(len(numbers)/2)
    return numbers[middle_index]

test1 = median([1,2,3])
print("expected result: 2, actual result: {}".format(test1))

test2 = median([1,2,3,4])
print("expected result: 2.5, actual result: {}".format(test2))

test3 = median([53, 12, 65, 7, 420, 317, 88])
print("expected result: 65, actual result: {}".format(test3))

单击“下一项”查看该练习的解决方案。